## Sid Meier's Civilization 4
## Copyright Firaxis Games 2005
##
## Implementaion of miscellaneous game functions
# < edited by the ViSa Modpack Team >

from CvPythonExtensions import *
import Popup as PyPopup

import sys
import CvUtil
import PyHelpers
import CvConfigParser

import pickle

################# SD-UTILITY-PACK ###################
import SdToolKit
sdEcho         = SdToolKit.sdEcho
sdModInit      = SdToolKit.sdModInit
sdModLoad      = SdToolKit.sdModLoad
sdModSave      = SdToolKit.sdModSave
sdEntityInit   = SdToolKit.sdEntityInit
sdEntityExists = SdToolKit.sdEntityExists
sdGetVal       = SdToolKit.sdGetVal
sdSetVal       = SdToolKit.sdSetVal
sdEntityWipe   = SdToolKit.sdEntityWipe


# globals
gc = CyGlobalContext()
localText = CyTranslator()
PyPlayer = PyHelpers.PyPlayer
PyInfo = PyHelpers.PyInfo

# Change this to either enable or disable the improve diplomatics relations
# ability of the great statesmen.
# Default value is true
g_bEnableImproveDiplomaticRelations = true

# Change this to either enable or disable the start insurrection ability of the 
# great statesmen.
# Default value is true
g_bEnableGreatStatesmanStartInsurrection = true

# Change this to true if great statesmen should be allowed to start 
# insurrections in friendly cities.
# Default value is false
g_bStartInsurrectionInFriendlyCities = false

# Change this to true if great statesmen should be allowed to start 
# insurrections in player capital cities.
# Default value is false
g_bStartInsurrectionInPlayerCapitals = false

# Change this to false if insurrections should not allow cities to flip to the
# player's civilization.
# Default value is true
g_bInsurrectionCanFlipCity = true

# Increase or decrease the value to change the base amount of time the 
# insurrection will last.
# Default value is 6
g_iBaseInsurrectionTime = 6

# Change this to false if the base insurrection time should be based on the
# player's cultural influence.
# Default value is true
g_bInsurrectionTimePlayerCultureBased = true

# Change this value to false if drill sergeants should not be required to train
# units in military bases.
# Default value is true
g_bDrillSergeantsRequiredToTrain = true

# Change this value to increase or decrease the amount of experience units will
# gain from the training given by drill sergeants who have joined a city. If 
# "Drill Sergeants Required To Train" is set to false then this value will 
# stack with the value in "Base Training Experience".
# Default value is 1
g_iDrillSergeantExperienceBonus = 1

# Change this value to increase or decrease the number of units each drill 
# sergeant can train. This value will only be used if drill sergeants are 
# required to train units.
# Default value is 3
g_iUnitsPerDrillSergeant = 3

# Change this value to false if units should get experience point every turn 
# instead of randomly, depending on their current level and experience points.
# Default value it true
g_bUnitsGainExperienceRandomly = true

# Change this value to increase or decrease the amount of experience units gain
# when they are trained successfully.
# Default value is 1
g_iBaseTrainingExperience = 1

# Change this value to false if there should be no max level that units can be
# trained in the military bases.
# Default value is true
g_bMaxLevelUnitTrainedCapped = True

# Change this value to increase or decrease the number of levels that units can
# be trained in the military bases.
# Default value is 5
g_iMaxLevelUnitTrained = 5

class CvActionButtonUtils:

	def __init__(self):
	
		global g_bEnableImproveDiplomaticRelations
		global g_bEnableGreatStatesmanStartInsurrection
		global g_bStartInsurrectionInFriendlyCities
		global g_bStartInsurrectionInPlayerCapitals
		global g_bInsurrectionCanFlipCity
		global g_iBaseInsurrectionTime
		global g_bInsurrectionTimePlayerCultureBased
		global g_bDrillSergeantsRequiredToTrain
		global g_iDrillSergeantExperienceBonus
		global g_iUnitsPerDrillSergeant
		global g_bUnitsGainExperienceRandomly
		global g_iBaseTrainingExperience
		global g_bMaxLevelUnitTrainedCapped
		global g_iMaxLevelUnitTrained
		

		config = CvConfigParser.CvConfigParser("ViSa_v3.ini")

		# Read the configuration values for the great statesman
		if(config != None):
			g_bEnableImproveDiplomaticRelations = config.getboolean("Great Statesman", "Enable Improve Diplomatic Relations", true)
			g_bEnableGreatStatesmanStartInsurrection = config.getboolean("Great Statesman", "Enable Great Statesman Start Insurrection", true)
			g_bStartInsurrectionInFriendlyCities = config.getboolean("Great Statesman", "Start Insurrection In Friendly Cities", false)
			g_bStartInsurrectionInPlayerCapitals = config.getboolean("Great Statesman", "Start Insurrection In Player Capitals", false)			
			g_bInsurrectionCanFlipCity = config.getboolean("Great Statesman", "Insurrection Can Flip City", true)
			g_iBaseInsurrectionTime = config.getint("Great Statesman", "Base Insurrection Time", 6)
			g_bInsurrectionTimePlayerCultureBased = config.getboolean("Great Statesman", "Insurrection Time Player Culture Based", true)
			g_bDrillSergeantsRequiredToTrain = config.getboolean("Military Bases", "Drill Sergeants Required To Train", true)
			g_iDrillSergeantExperienceBonus = config.getint("Military Bases", "Drill Sergeant Experience Bonus", 1)
			g_iUnitsPerDrillSergeant = config.getint("Military Bases", "Units Per Drill Sergeant", 3)
			g_bUnitsGainExperienceRandomly = config.getboolean("Military Bases", "Units Gain Experience Randomly", true)
			g_iBaseTrainingExperience = config.getint("Military Bases", "Base Training Experience", 1)
			g_bMaxLevelUnitTrainedCapped = config.getboolean("Military Bases", "Max Level Unit Trained Capped", true)
			g_iMaxLevelUnitTrained = config.getint("Military Bases", "Max Level Unit Trained", 5)


	def canImproveDiplomaticRelations(self, objUnit):

		if(not g_bEnableImproveDiplomaticRelations):
			return False
			
		if(objUnit == None):
			return False
			
		if(objUnit.isNone()):
			return False

		if(objUnit.getUnitType() != gc.getInfoTypeForString("UNIT_STATESMAN")):
			return False
		
		objPlayerTeam = gc.getTeam(objUnit.getTeam())
		
		if(objPlayerTeam.getHasMetCivCount(True) == 0):
			return False
			
		return True
	
	
	def canStartInsurrection(self, objUnit):

		if(not g_bEnableGreatStatesmanStartInsurrection):
			return False
				
		if(objUnit == None):
			return False
			
		if(objUnit.isNone()):
			return False

		if(objUnit.getUnitType() != gc.getInfoTypeForString("UNIT_STATESMAN")):
			return False
		
		objPlot = objUnit.plot()
		
		if(objPlot.getOwner() == objUnit.getOwner()):
			return False	

		if(objPlot.getOwner() != objUnit.getOwner() and not objPlot.isCity()):
			return False

		objCity = objPlot.getPlotCity()
		
		objPlayerTeam = gc.getTeam(objUnit.getTeam())
		
		if(not g_bStartInsurrectionInFriendlyCities and objPlot.getOwner() != objUnit.getOwner() and objPlot.isCity() and not objPlayerTeam.isAtWar(objCity.getOwner())):
			return False

		if(not g_bStartInsurrectionInPlayerCapitals and objCity.isCapital()):
			return False

		return True
												
												
	# This method will handle the case where the improve diplomatic relations action button is pressed.
	def eventDiplomaticsRelationsPopup(self): 
		player = gc.getActivePlayer()
		iPlayer = player.getID()
		
		popup = PyPopup.PyPopup(CvUtil.DIPLOMATIC_RELATIONS_POPUP, EventContextTypes.EVENTCONTEXT_ALL)
		popup.setHeaderString("Select a leader to improve diplomatic relations with:")

		# Go through all of the players in the game.
		for iLoopPlayer in range(gc.getMAX_PLAYERS()):

			# Only allow players that are alive, not the current active player or a minor civilization
			if (gc.getPlayer(iLoopPlayer).isAlive() and iLoopPlayer != iPlayer and not gc.getPlayer(iLoopPlayer).isBarbarian() and  not gc.getPlayer(iLoopPlayer).isMinorCiv()):

				# Add any players that have met the currently active player to the popup
				if (gc.getTeam(gc.getPlayer(iLoopPlayer).getTeam()).isHasMet(player.getTeam())):

					gc.getPlayer(iLoopPlayer).AI_setAttitudeExtra(iPlayer, 0)
					
					# Get the current player's attitude towards the active player
					strButtonText = str(gc.getAttitudeInfo(gc.getPlayer(iLoopPlayer).AI_getAttitude(iPlayer)).getDescription())
					
					strHelpText = str("Improve diplomatic relations with " + gc.getPlayer(iLoopPlayer).getName())
					
					# Get the information if the current player is at war with the currently active player
					if(gc.getTeam(gc.getPlayer(iLoopPlayer).getTeam()).isAtWar(player.getTeam())):
						strButtonText = strButtonText + " and at war"
						strHelpText = str("Make peace with " + gc.getPlayer(iLoopPlayer).getName())
						
					strButtonText = strButtonText + " with you."
					
					leaderHeadImage = str(gc.getLeaderHeadInfo( gc.getPlayer(iLoopPlayer).getLeaderType() ).getButton())					
					popup.addPythonButton( str(gc.getPlayer(iLoopPlayer).getName()), strButtonText, strHelpText, leaderHeadImage, iLoopPlayer, -1, true  )
					
		popup.launch(true)
	 	
	
	def startInsurrection(self, objUnit):
				
		if(objUnit == None):
			return
			
		if(objUnit.isNone()):
			return
			
		if(objUnit.getUnitType() != gc.getInfoTypeForString("UNIT_STATESMAN")):
			return 
			
		targetPlot = objUnit.plot()
		
		targetCity = targetPlot.getPlotCity()
		
		iActivePlayer = objUnit.getOwner()
		iTargetPlayer = targetPlot.getOwner()
		targetPlayer = gc.getPlayer(iTargetPlayer)
		
		iActivePlayerCityCulture = float(targetPlot.getCulture(iActivePlayer))
		iTargetPlayerCityCulture = float(targetPlot.getCulture(iTargetPlayer))
		iTotalCityCulture = float(targetPlot.countTotalCulture())
		iActivePlayerPercentCulture = int((iActivePlayerCityCulture/iTotalCityCulture) * 100)
		
		iInsurrectionTime = g_iBaseInsurrectionTime
		
		if(g_bInsurrectionTimePlayerCultureBased):
			iInsurrectionTime = int(iInsurrectionTime + ((iTotalCityCulture/iTargetPlayerCityCulture) * (0.1 * iActivePlayerCityCulture)))

		if(g_bInsurrectionCanFlipCity):
			iFlipChance = gc.getGame().getMapRand().getInt(int(iTotalCityCulture),"Great Statesman")
			if(iActivePlayerCityCulture >= iFlipChance):
				targetPlot.setOwner(iActivePlayer)
				return
		
		targetCity.changeOccupationTimer(iInsurrectionTime)

		strMessage = objUnit.getNameNoDesc() + " has started an insurrection in " + targetCity.getName()
		
		# Inform the player that their great statesman has started an insurrection.
		CyInterface().addMessage(iActivePlayer, False, 0, strMessage, "", 0, gc.getUnitInfo(objUnit.getUnitType()).getButton(), ColorTypes(0), targetCity.getX(), targetCity.getY(), True, True) 

		objUnit.kill(true, objUnit.getOwner())


	# Returns true if the unit can be trained, false otherwise.
	def canTrainUnit(self, objUnit):
	
		# Return false immediately if the unit passed in is invalid
		if(objUnit == None):
			return false
			
		# Return false immediately if the unit passed in is invalid	
		if(objUnit.isNone()):
			return false

		# Return false if the unit is past the point of training
		if(g_bMaxLevelUnitTrainedCapped and objUnit.getLevel() >= g_iMaxLevelUnitTrained):
			return false
		
		# Return false if the unit cannot move
		if(not objUnit.canMove()):
			return false
			
		# Get the plot the unit is on
		objPlot = objUnit.plot()
		
		# Return false if the unit passed in is not in a city
		if(not objPlot.isCity()):
			return false
		
		# Return false if the unit is already training
		if(objUnit.getGroup().getActivityType() == ActivityTypes.ACTIVITY_SLEEP):
			return false
			
		# Return false if the unit is ready to be promoted
		if(objUnit.isPromotionReady()):
			return false
			
		# Get the city on the plot
		objCity = objPlot.getPlotCity()

		# Get the number of drill sergeants in the city
		iDrillSergeantCount = objCity.getFreeSpecialistCount(gc.getInfoTypeForString("SPECIALIST_DRILL_SERGEANT"))
		
		# Calculate the maximum number units that can be trained in the city.
		iMaxUnitTrainingCount = g_iUnitsPerDrillSergeant * iDrillSergeantCount
		
		# Return false if the city the unit is in does not belong to the units
		# owner
		if(objCity.getOwner() != objUnit.getOwner()):
			return false
					
		# Return false if drill sergeants are required to train units and the 
		# city the unit is at doesn't have any.
		if(g_bDrillSergeantsRequiredToTrain and iDrillSergeantCount == 0):
			return false
				
		# Get the number of units currrently being trained in the city.
		iTrainingCount = self.getNumCityTrainingUnits(objCity)
		
		# Return false if drill sergeants are required to train units and the
		# number of units being trained in the city has reached the maximum
		# amount
		if(g_bDrillSergeantsRequiredToTrain and iTrainingCount >= iMaxUnitTrainingCount):
			return false
		
		# Return false if the unit is a land unit and the city doesn't have an 
		# army base
		if(not objCity.hasBuilding(gc.getInfoTypeForString("BUILDING_ARMYBASE")) and objUnit.getDomainType() == gc.getInfoTypeForString("DOMAIN_LAND")):
			return false

		# Return false if the unit is a sea unit and the city doesn't have a 
 		# navy base
		elif(not objCity.hasBuilding(gc.getInfoTypeForString("BUILDING_NAVYBASE")) and objUnit.getDomainType() == gc.getInfoTypeForString("DOMAIN_SEA")):
			return false

 		# Return false if the unit is an air unit and the city doesn't have an 
 		# airforce base
		elif(not objCity.hasBuilding(gc.getInfoTypeForString("BUILDING_AIRFORCEBASE")) and objUnit.getDomainType() == gc.getInfoTypeForString("DOMAIN_AIR")):
			return false
			
		return true


	# Try to train the units in the city
	def trainCityUnits(self, objCity):
	
		# Get the list of units in the city			
		unitList = self.getCityUnitList(objCity)
		
		# Go through each unit in the list and try to train them
		for objUnit in unitList:
			self.trainUnit(objUnit)
			
	
	# Train the unit giving it experience points if needed
	def trainUnit(self, objUnit):

		# Return immediately if the unit passed in is invalid
		if(objUnit == None):
			return
		
		# Return immediately if the unit passed in is invalid
		if(objUnit.isNone()):
			return 

		# Return immediately if the unit isn't being tracked.
		if(sdEntityExists("Military Bases",self.getUniqueUnitID(objUnit)) == False):
			return
		
		# If the unit isn't sleeping but is being tracked, stop tracking the 
		# unit and return immediately
		if(objUnit.getGroup().getActivityType() != ActivityTypes.ACTIVITY_SLEEP):
			sdEntityWipe("Military Bases", self.getUniqueUnitID(objUnit))	
			return
			
		# Return if the unit is ready to be promoted
		if(objUnit.isPromotionReady()):
			objSelectionGroup = objUnit.getGroup()
			objSelectionGroup.setActivityType(ActivityTypes.ACTIVITY_AWAKE)
			objUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_WAKE"))
			return
		
		# Get the experience needed for the unit
		iExperienceNeeded = objUnit.experienceNeeded()

		iUnitLevel = objUnit.getLevel()
		iFortifyCount = sdGetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyCount")
		iFortifyTarget = sdGetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyTarget")
				
		# If the unit is ready to be given experience points then
		if(iFortifyTarget <= iFortifyCount):
			# Reset the script data
			sdSetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyTarget", self.getNextFortifyTarget(objUnit))
			sdSetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyCount",0)

			# Give the unit its experience point(s)
			if(g_bMaxLevelUnitTrainedCapped):
				objUnit.setExperience(objUnit.getExperience()+self.getUnitExperiencePoints(),iExperienceNeeded)
			else:
				objUnit.setExperience(objUnit.getExperience()+self.getUnitExperiencePoints(),objUnit.getExperience()+self.getUnitExperiencePoints())

		if(iFortifyTarget > iFortifyCount):
			sdSetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyCount",iFortifyCount+1)
		
	
	# Setup the unit to start training it		
	def startTrainingUnit(self, objUnit):

		# Return immediately if the unit passed in is invalid
		if(objUnit == None):
			return
		
		# Return immediately if the unit passed in is invalid
		if(objUnit.isNone()):
			return 

		# Return immediately if the unit cannot be trained
		if(not self.canTrainUnit(objUnit)):

			# Wipe out the units script data if it is being tracked
			if(sdEntityExists("Military Bases",self.getUniqueUnitID(objUnit))):
				sdEntityWipe("Military Bases", self.getUniqueUnitID(objUnit))	
			return

		# Reset the script data if it was previously set
		if(sdEntityExists("Military Bases",self.getUniqueUnitID(objUnit))):
			sdSetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyTarget", self.getNextFortifyTarget(objUnit))
			sdSetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyCount",0)

		else:
			self.setupScript(objUnit)	

		objSelectionGroup = objUnit.getGroup()
		objSelectionGroup.setActivityType(ActivityTypes.ACTIVITY_SLEEP)
		objUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_SLEEP"))
		objUnit.finishMoves()
		CyInterface().clearSelectionList()
		
	# Returns a unique ID comprised of the player ID and the unit ID.
	def getUniqueUnitID(self, objUnit):
	
		# Return INVALID immediately if the unit passed in is invalid
		if(objUnit == None):
			return "INVALID"
		
		# Return INVALID immediately if the unit passed in is invalid
		if(objUnit.isNone()):
			return "INVALID"
			
		# Return the string that is the <playerID>-<unitID>
		return str(objUnit.getOwner())+"-"+str(objUnit.getID())
		
		
	# Setup the script data for the unit passed in
	def setupScript(self, objUnit):

		# Return immediately if the unit passed in is invalid
		if(objUnit == None):
			return
		
		# Return immediately if the unit passed in is invalid
		if(objUnit.isNone()):
			return 
			
		scriptDict = { 	"FortifyCount" : 0,
					"FortifyTarget" : self.getNextFortifyTarget(None) }
		sdEntityInit("Military Bases", self.getUniqueUnitID(objUnit), scriptDict)
	
	
	# Returns the list of units in a city.	
	def getCityUnitList(self, objCity):

		# Return an empty list immediately if the city passed in is invalid
		if(objCity == None):
			return []
		
		# Return an empty list immediately if the city passed in is invalid
		if(objCity.isNone()):
			return []
	
		unitList = []
		
		objPlot = objCity.plot()
		
		for i in range(objPlot.getNumUnits()):
			unitList.append(objPlot.getUnit(i))
			
		return unitList
	
	
	# Returns the number units training in a city
	def getNumCityTrainingUnits(self, objCity):
	
		# Return 0 immediately if the city passed in is invalid
		if(objCity == None):
			return []
		
		# Return 0 immediately if the city passed in is invalid
		if(objCity.isNone()):
			return []
	
		# Get the units at the city
		unitList = self.getCityUnitList(objCity)
	
		iTrainingCount = 0
		
		# Go through all of the units in the list
		for objUnit in unitList:

			# Increment the training count if the units are being tracked
			if(sdEntityExists("Military Bases",self.getUniqueUnitID(objUnit))):
				if(objUnit.getGroup().getActivityType() == ActivityTypes.ACTIVITY_SLEEP):
					iTrainingCount += 1
				else:
					sdEntityWipe("Military Bases", self.getUniqueUnitID(objUnit))	
							
		return iTrainingCount
		

	# Return the number of experience points the unit will get
	def getUnitExperiencePoints(self):
	
		iExperiencePoints = g_iBaseTrainingExperience
		
		# If drill sergeants are not required to train units then add their
		# bonus amount to the amount that will be returned
		if(not g_bDrillSergeantsRequiredToTrain):
			iExperiencePoints += g_iDrillSergeantExperienceBonus

		return iExperiencePoints
		
	
	# Returns the next fortify target amount or how many units needs to wait to
	# get its experience points
	def getNextFortifyTarget(self, objUnit):

		# Return 0 immediately if the unit should gain experience every turn
		if(not g_bUnitsGainExperienceRandomly):
			return 0
		
		# Return a number between 0-9 immediately if the unit passed in is 
		# invalid
		if(objUnit == None):
			return gc.getGame().getMapRand().get(5, "Military Bases")
		
		# Return a number between 0-9 immediately if the unit passed in is 
		# invalid
		if(objUnit.isNone()):
			return gc.getGame().getMapRand().get(5, "Military Bases")
			
		iFortifyCount = sdGetVal("Military Bases", self.getUniqueUnitID(objUnit), "FortifyCount")

		iUnitLevel = objUnit.getLevel()

		# Get a random number, anywhere from 0 to the value of fortifyCount
		iRandVal = gc.getGame().getMapRand().get(iFortifyCount, "Military Bases")

		iGameSpeed = gc.getGame().getGameSpeedType()

		if(iGameSpeed < 0):
			iGameSpeed = 1

		iFortifyTarget = ((iUnitLevel + iRandVal) * iGameSpeed)

		return iFortifyTarget
